﻿/*
 * Copyright (c) HiSilicon (Shanghai) Technologies Co., Ltd. 2023-2023. All rights reserved.
 */

#include "readhex.h"

using namespace std;
using namespace MemoryClass;
namespace {
    const int SYSTEM_NUM_16 = 16;
    const int ASCII_LOWERCASE_A = 97;
    const int ASCII_UPPERCASE_A = 65;
    const int ASCII_LOWERCASE_F = 102;
    const int ASCII_UPPERCASE_F = 70;
    const int ASCII_0 = 48;
    const int ASCII_9 = 57;
    uint8_t *g_s;
    int g_baseAddress = 0x0;
    int32_t g_startSegmentCs = -1;
    int32_t g_startSegmentIp = -1;
    int64_t g_startExecution = -1;       // must be int64, overflow risk with int32
    int g_numBytes;
    int g_address;
    uint8_t g_checksum;
    char g_line[600]; // Each line can carry a maximum of 600 bytes of data.
    int g_lineNumber = 0;
    int g_startAddress = -1;
    int g_endAddress = -1;
    int g_dataOffset = 0;
    int g_dataStartAddress = -1;
    uint8_t g_type;

    enum RECORD_TYPE : int {
        RECORD_DATA = 0x00,
        RECORD_END_OF_FILE = 0x01,
        RECORD_EXTENDED_SEGMENT_ADDRESS = 0x02,
        RECORD_START_SEGMENT_ADDRESS = 0x03,
        RECORD_EXTENDED_LINEAR_ADDRESS = 0x04,
        RECORD_START_LINEAR_ADDRESS = 0x05
    };
}

static uint8_t GetNybble(const uint8_t s)
{
    int word = s;
    int value;
    int offset10 = 10;
    if (word >= ASCII_0 && s <= ASCII_9) {
        value = word - ASCII_0;
    } else if (word >= ASCII_LOWERCASE_A && word <= ASCII_LOWERCASE_F) {
        value = word - ASCII_LOWERCASE_A + offset10;
    } else if (word >= ASCII_UPPERCASE_A && word <= ASCII_UPPERCASE_F) {
        value = word - ASCII_UPPERCASE_A + offset10;
    } else {
        printf("Invalid hex character: %c\n", s);
        value = 0;
    }
    return value;
}

static uint8_t GetByte(const uint8_t *s)
{
    return GetNybble(*s) * SYSTEM_NUM_16 + GetNybble(*s + 1);
}

static uint16_t GetWord(uint8_t *s)
{
    /* move 2 byte offset */
    return GetByte(s) * static_cast<uint16_t>(SYSTEM_NUM_16 * SYSTEM_NUM_16) + GetByte(s + 2);
}

void MemoryAddressesStore(void *arg, int address, unsigned char c)
{
    Q_UNUSED(c);
    MemoryAddresses *mi = (struct MemoryAddresses *)arg;
    int *oldBuf = new (std::nothrow)int[sizeof(int) * (mi->count)];
    if (oldBuf == nullptr) {
        return;
    }
    for (int i = 0; i < mi->count; i++) {
        *(oldBuf + i) = *(mi->addresses + i);
    }
    delete[] mi->addresses;
    mi->addresses = new (std::nothrow)int[sizeof(int) * (mi->count + 1)];
    if (mi->addresses == nullptr) {
        delete[] oldBuf;
        return;
    }

    for (int i = 0; i < mi->count; i++) {
        *(mi->addresses + i) = *(oldBuf + i);
    }
    mi->addresses[mi->count++] = address;
    delete[] oldBuf;
}

int ReadHex(FILE *f, MemoryFunc memory, void *memoryArg, int badChecksumIsError)
{
    g_baseAddress = 0x0;
    g_startSegmentCs = -1;
    g_startSegmentIp = -1;
    g_startExecution = -1;       // must be int64, overflow risk with int32
    g_lineNumber = 0;
    g_startAddress = -1;
    g_endAddress = -1;
    g_dataOffset = 0;
    g_dataStartAddress = -1;

    while (fgets(g_line, sizeof(g_line), f) != nullptr) {
        g_s = reinterpret_cast <uint8_t *>(g_line);
        g_lineNumber++;
        if (*g_s != ':') {
            return 0;
        }
        g_s++;

        if (!GetLineAddress(&g_s)) {
            break;
        }
        switch (g_type) {
            case RECORD_DATA:
                RecodeData(memory, memoryArg, &g_s);
                break;
            case RECORD_END_OF_FILE:
                break;
            case RECORD_EXTENDED_SEGMENT_ADDRESS:
                RecordExtendedSegMentAddress(g_baseAddress, g_checksum, &g_s);
                break;
            case RECORD_START_SEGMENT_ADDRESS:
                RecordStartSegMentAddress(g_startSegmentCs, g_startSegmentIp, g_checksum, &g_s);
                break;
            case RECORD_EXTENDED_LINEAR_ADDRESS:
                RecordExtendedLinearAddress(g_startAddress, g_endAddress, g_dataOffset, g_checksum, &g_s);
                break;
            case RECORD_START_LINEAR_ADDRESS:
                RecordStartLinearAddress(g_startExecution, g_checksum, &g_s);
                break;
            default:
                return 0;
        }
        if (VerifyChecksum(g_checksum, g_s, badChecksumIsError) == 0) {
            return 0;
        }
    }
    MemoryAddresses *mi = (struct MemoryAddresses *)memoryArg;
    mi->count = g_endAddress - g_startAddress + g_dataOffset - g_dataStartAddress;
    return 1;
}

bool GetLineAddress(uint8_t **s)
{
    g_checksum = 0;
    g_numBytes = GetByte(*s);
    if (g_numBytes == 0) {
        return false;
    }

    g_checksum += static_cast<uint8_t>(g_numBytes);
    *s += 2; /* move 2 byte offset */

    g_address = GetWord(*s);
    g_checksum += static_cast<uint8_t>(GetByte(*s));
    g_checksum += static_cast<uint8_t>(GetByte(*s + 2)); /* move 2 byte offset */
    *s += 4; /* move 4 byte offset */

    g_type = GetByte(*s);
    g_checksum += static_cast<uint8_t>(g_type);
    *s += 2; /* move 2 byte offset */
    return true;
}

void RecodeData(MemoryFunc memory, void *memoryArg, uint8_t **s)
{
    if (g_dataStartAddress == -1) {
        g_dataStartAddress = g_address;
    }
    g_dataOffset = g_address + g_numBytes;
    for (int i = 0; i < g_numBytes; i++) {
        uint8_t data = GetByte(*s);
        g_checksum += static_cast<uint8_t>(data);
        *s += 2; /* move 2 byte offset */
        memory(memoryArg, g_baseAddress + g_address, data);
        g_address++;
    }
}

void RecordStartLinearAddress(int64_t &startExecution, uint8_t &checksum, uint8_t **s)
{
    /* move right 16 offset  move 4 byte offset */
    startExecution = (static_cast<uint32_t>(GetWord(*s)) << 16) + GetWord(*s + 4);
    checksum += static_cast<uint8_t>(GetByte(*s));
    checksum += static_cast<uint8_t>(GetByte(*s + 2)); /* move 2 byte offset */
    checksum += static_cast<uint8_t>(GetByte(*s + 4)); /* move 4 byte offset */
    checksum += static_cast<uint8_t>(GetByte(*s + 6)); /* move 6 byte offset */
    *s += 8; /* move 8 byte offset */
}

void RecordExtendedLinearAddress(int &startAddress, int &endAddress, int &dataOffset, uint8_t &checksum, uint8_t **s)
{
    g_baseAddress = static_cast<uint32_t>(GetWord(*s)) << 16; /* move right 16 offset */
    checksum += static_cast<uint8_t>(GetByte(*s));
    checksum += static_cast<uint8_t>(GetByte(*s + 2)); /* move 2 byte offset */
    *s += 4; /* move 4 byte offset */
    dataOffset = 0;
    if (startAddress == -1) {
        startAddress = g_baseAddress;
    }
    if (endAddress < g_baseAddress) {
        endAddress = g_baseAddress;
    }
}

void RecordStartSegMentAddress(int32_t &startSegmentCs, int32_t &startSegmentIp, uint8_t &checksum, uint8_t **s)
{
    startSegmentCs = GetWord(*s);
    checksum += static_cast<uint8_t>(GetByte(*s));
    checksum += static_cast<uint8_t>(GetByte(*s + 2)); /* move 2 byte offset */
    *s += 4; /* move 4 byte offset */
    startSegmentIp = GetWord(*s);
    checksum += static_cast<uint8_t>(GetByte(*s));
    checksum += static_cast<uint8_t>(GetByte(*s + 2)); /* move 2 byte offset */
    *s += 4; /* move 4 byte offset */
}

void RecordExtendedSegMentAddress(int baseAddress, uint8_t &checksum, uint8_t **s)
{
    baseAddress = static_cast<uint32_t>(GetWord(*s)) << 4; /* move right 4 offset */
    checksum += static_cast<uint8_t>(GetByte(*s));
    checksum += static_cast<uint8_t>(GetByte(*s + 2)); /* move 2 byte offset */
    *s += 4; /* move 4 byte offset */
}

int VerifyChecksum(uint8_t &checksum, uint8_t *s, int badChecksumIsError)
{
    checksum = ~checksum + 1;
    uint8_t fileChecksum = GetByte(s);
    if (checksum != fileChecksum) {
        if (badChecksumIsError != 0) {
            return 0;
        }
    }
    return 1;
}

void MemoryDescStore(void *arg, int address, uint8_t c)
{
    struct MemoryDesc *mi = (struct MemoryDesc *)arg;
    if (address >= static_cast<int>(mi->offset) &&
        address < (static_cast<int>(mi->offset) + static_cast<int>(mi->size))) {
        int offset = address - static_cast<int>(mi->offset);
        mi->p[offset] = c;
        if (static_cast<int>(offset) + 1 > static_cast<int>(mi->sizeWritten)) {
            mi->sizeWritten = static_cast<size_t>(offset + 1);
        }
    }
}
